/*
 * Copyright 2020 Google LLC.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/gpu/ganesh/ops/AtlasInstancedHelper.h"

#include "src/gpu/BufferWriter.h"
#include "src/gpu/KeyBuilder.h"
#include "src/gpu/ganesh/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/ganesh/glsl/GrGLSLVarying.h"
#include "src/gpu/ganesh/glsl/GrGLSLVertexGeoBuilder.h"

using namespace skia_private;

namespace skgpu::ganesh {
void AtlasInstancedHelper::getKeyBits(KeyBuilder *b) const
{
    b->addBits(kNumShaderFlags, (int)fShaderFlags, "atlasFlags");
}

void AtlasInstancedHelper::appendInstanceAttribs(TArray<GrGeometryProcessor::Attribute> *instanceAttribs) const
{
    instanceAttribs->emplace_back("locations", kFloat4_GrVertexAttribType, SkSLType::kFloat4);
    if (fShaderFlags & ShaderFlags::kCheckBounds) {
        instanceAttribs->emplace_back("sizeInAtlas", kFloat2_GrVertexAttribType, SkSLType::kFloat2);
    }
}

void AtlasInstancedHelper::writeInstanceData(VertexWriter *instanceWriter, const Instance *i) const
{
    SkASSERT(i->fLocationInAtlas.x() >= 0);
    SkASSERT(i->fLocationInAtlas.y() >= 0);
    *instanceWriter <<
        // A negative x coordinate in the atlas indicates that the path is transposed.
        // Also add 1 since we can't negate zero.
        ((float)(i->fTransposedInAtlas ? -i->fLocationInAtlas.x() - 1 : i->fLocationInAtlas.x() + 1)) <<
        (float)i->fLocationInAtlas.y() << (float)i->fPathDevIBounds.left() << (float)i->fPathDevIBounds.top() <<
        VertexWriter::If(fShaderFlags & ShaderFlags::kCheckBounds, SkSize::Make(i->fPathDevIBounds.size()));
}

void AtlasInstancedHelper::injectShaderCode(const GrGeometryProcessor::ProgramImpl::EmitArgs &args,
    const GrShaderVar &devCoord, GrGLSLUniformHandler::UniformHandle *atlasAdjustUniformHandle) const
{
    GrGLSLVarying atlasCoord(SkSLType::kFloat2);
    args.fVaryingHandler->addVarying("atlasCoord", &atlasCoord);

    const char *atlasAdjustName;
    *atlasAdjustUniformHandle = args.fUniformHandler->addUniform(nullptr, kVertex_GrShaderFlag, SkSLType::kFloat2,
        "atlas_adjust", &atlasAdjustName);

    args.fVertBuilder->codeAppendf(
        // A negative x coordinate in the atlas indicates that the path is transposed.
        // We also added 1 since we can't negate zero.
        "float2 atlasTopLeft = float2(abs(locations.x) - 1, locations.y);"
        "float2 devTopLeft = locations.zw;"
        "bool transposed = locations.x < 0;"
        "float2 atlasCoord = %s - devTopLeft;"
        "if (transposed) {"
        "atlasCoord = atlasCoord.yx;"
        "}"
        "atlasCoord += atlasTopLeft;"
        "%s = atlasCoord * %s;",
        devCoord.c_str(), atlasCoord.vsOut(), atlasAdjustName);

    if (fShaderFlags & ShaderFlags::kCheckBounds) {
        GrGLSLVarying atlasBounds(SkSLType::kFloat4);
        args.fVaryingHandler->addVarying("atlasbounds", &atlasBounds, GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
        args.fVertBuilder->codeAppendf(R"(
        float4 atlasBounds = atlasTopLeft.xyxy + (transposed ? sizeInAtlas.00yx
                                                             : sizeInAtlas.00xy);
        %s = atlasBounds * %s.xyxy;)",
            atlasBounds.vsOut(), atlasAdjustName);

        args.fFragBuilder->codeAppendf("half atlasCoverage = 0;"
            "float2 atlasCoord = %s;"
            "float4 atlasBounds = %s;"
            "if (all(greaterThan(atlasCoord, atlasBounds.xy)) &&"
            "all(lessThan(atlasCoord, atlasBounds.zw))) {"
            "atlasCoverage = ",
            atlasCoord.fsIn(), atlasBounds.fsIn());
        args.fFragBuilder->appendTextureLookup(args.fTexSamplers[0], "atlasCoord");
        args.fFragBuilder->codeAppendf(R"(.a;
        })");
    } else {
        args.fFragBuilder->codeAppendf("half atlasCoverage = ");
        args.fFragBuilder->appendTextureLookup(args.fTexSamplers[0], atlasCoord.fsIn());
        args.fFragBuilder->codeAppendf(".a;");
    }

    if (fShaderFlags & ShaderFlags::kInvertCoverage) {
        args.fFragBuilder->codeAppendf("%s *= (1 - atlasCoverage);", args.fOutputCoverage);
    } else {
        args.fFragBuilder->codeAppendf("%s *= atlasCoverage;", args.fOutputCoverage);
    }
}

void AtlasInstancedHelper::setUniformData(const GrGLSLProgramDataManager &pdman,
    const GrGLSLUniformHandler::UniformHandle &atlasAdjustUniformHandle) const
{
    SkASSERT(fAtlasProxy->isInstantiated());
    SkISize dimensions = fAtlasProxy->backingStoreDimensions();
    pdman.set2f(atlasAdjustUniformHandle, 1.f / dimensions.width(), 1.f / dimensions.height());
}
} // namespace skgpu::ganesh
